/*
 * Copyright (c) 2013 Tobias Markmann
 * Licensed under the simplified BSD license.
 * See Documentation/Licenses/BSD-simplified.txt for more information.
 */

/*
 * Copyright (c) 2014 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */

#include <Swift/Controllers/BlockListController.h>

#include <boost/bind.hpp>

#include <Swiften/Client/ClientBlockListManager.h>

#include <Swiften/Base/foreach.h>
#include <Swiften/Base/format.h>
#include <Swift/Controllers/Intl.h>
#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h>
#include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
#include <Swift/Controllers/XMPPEvents/ErrorEvent.h>
#include <Swift/Controllers/UIInterfaces/BlockListEditorWidget.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>

namespace Swift {

BlockListController::BlockListController(ClientBlockListManager* blockListManager, UIEventStream* uiEventStream, BlockListEditorWidgetFactory* blockListEditorWidgetFactory, EventController* eventController) : blockListManager_(blockListManager), blockListEditorWidgetFactory_(blockListEditorWidgetFactory), blockListEditorWidget_(0), eventController_(eventController), remainingRequests_(0), uiEventStream_(uiEventStream) {
	uiEventStream->onUIEvent.connect(boost::bind(&BlockListController::handleUIEvent, this, _1));
	blockListManager_->getBlockList()->onItemAdded.connect(boost::bind(&BlockListController::handleBlockListChanged, this));
	blockListManager_->getBlockList()->onItemRemoved.connect(boost::bind(&BlockListController::handleBlockListChanged, this));
}

BlockListController::~BlockListController() {
	uiEventStream_->onUIEvent.disconnect(boost::bind(&BlockListController::handleUIEvent, this, _1));
	blockListManager_->getBlockList()->onItemAdded.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this));
	blockListManager_->getBlockList()->onItemRemoved.disconnect(boost::bind(&BlockListController::handleBlockListChanged, this));
}

void BlockListController::blockListDifferences(const std::vector<JID> &newBlockList, std::vector<JID> &jidsToUnblock, std::vector<JID> &jidsToBlock) const {
	foreach (const JID& jid, blockListBeforeEdit) {
		if (std::find(newBlockList.begin(), newBlockList.end(), jid) == newBlockList.end()) {
			jidsToUnblock.push_back(jid);
		}
	}

	foreach (const JID& jid, newBlockList) {
		if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) {
			jidsToBlock.push_back(jid);
		}
	}
}

void BlockListController::handleUIEvent(boost::shared_ptr<UIEvent> rawEvent) {
	// handle UI dialog
	boost::shared_ptr<RequestBlockListDialogUIEvent> requestDialogEvent = boost::dynamic_pointer_cast<RequestBlockListDialogUIEvent>(rawEvent);
	if (requestDialogEvent != NULL) {
		if (blockListEditorWidget_ == NULL) {
			blockListEditorWidget_ = blockListEditorWidgetFactory_->createBlockListEditorWidget();
			blockListEditorWidget_->onSetNewBlockList.connect(boost::bind(&BlockListController::handleSetNewBlockList, this, _1));
		}
		blockListBeforeEdit = blockListManager_->getBlockList()->getItems();
		blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit);
		blockListEditorWidget_->setError("");
		blockListEditorWidget_->show();
		return;
	}

	// handle block state change
	boost::shared_ptr<RequestChangeBlockStateUIEvent> changeStateEvent = boost::dynamic_pointer_cast<RequestChangeBlockStateUIEvent>(rawEvent);
	if (changeStateEvent != NULL) {
		if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Blocked) {
			GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDRequest(changeStateEvent->getContact());
			blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false));
			blockRequest->send();
		} else if (changeStateEvent->getBlockState() == RequestChangeBlockStateUIEvent::Unblocked) {
			GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDRequest(changeStateEvent->getContact());
			unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, std::vector<JID>(1, changeStateEvent->getContact()), false));
			unblockRequest->send();
		}
		return;
	}
}

void BlockListController::handleBlockResponse(GenericRequest<BlockPayload>::ref request, boost::shared_ptr<BlockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) {
	if (error) {
		std::string errorMessage;
		// FIXME: Handle reporting of list of JIDs in a translatable way.
		errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to block %1%.")) % jids.at(0).toString());
		if (!error->getText().empty()) {
			errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText());
		}
		if (blockListEditorWidget_ && originEditor) {
			blockListEditorWidget_->setError(errorMessage);
			blockListEditorWidget_->setBusy(false);
		}
		else {
			eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage));
		}
	}
	if (originEditor) {
		remainingRequests_--;
		if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) {
			blockListEditorWidget_->setBusy(false);
			blockListEditorWidget_->hide();
		}
	}
}

void BlockListController::handleUnblockResponse(GenericRequest<UnblockPayload>::ref request, boost::shared_ptr<UnblockPayload>, ErrorPayload::ref error, const std::vector<JID>& jids, bool originEditor) {
	if (error) {
		std::string errorMessage;
		// FIXME: Handle reporting of list of JIDs in a translatable way.
		errorMessage = str(format(QT_TRANSLATE_NOOP("", "Failed to unblock %1%.")) % jids.at(0).toString());
		if (!error->getText().empty()) {
			errorMessage = str(format(QT_TRANSLATE_NOOP("", "%1%: %2%.")) % errorMessage % error->getText());
		}
		if (blockListEditorWidget_ && originEditor) {
			blockListEditorWidget_->setError(errorMessage);
			blockListEditorWidget_->setBusy(false);
		}
		else {
			eventController_->handleIncomingEvent(boost::make_shared<ErrorEvent>(request->getReceiver(), errorMessage));
		}
	}
	if (originEditor) {
		remainingRequests_--;
		if (blockListEditorWidget_ && (remainingRequests_ == 0) && !error) {
			blockListEditorWidget_->setBusy(false);
			blockListEditorWidget_->hide();
		}
	}
}

void BlockListController::handleSetNewBlockList(const std::vector<JID> &newBlockList) {
	std::vector<JID> jidsToBlock;
	std::vector<JID> jidsToUnblock;

	blockListDifferences(newBlockList, jidsToUnblock, jidsToBlock);

	if (!jidsToBlock.empty()) {
		remainingRequests_++;
		GenericRequest<BlockPayload>::ref blockRequest = blockListManager_->createBlockJIDsRequest(jidsToBlock);
		blockRequest->onResponse.connect(boost::bind(&BlockListController::handleBlockResponse, this, blockRequest, _1, _2, jidsToBlock, true));
		blockRequest->send();
	}
	if (!jidsToUnblock.empty()) {
		remainingRequests_++;
		GenericRequest<UnblockPayload>::ref unblockRequest = blockListManager_->createUnblockJIDsRequest(jidsToUnblock);
		unblockRequest->onResponse.connect(boost::bind(&BlockListController::handleUnblockResponse, this, unblockRequest, _1, _2, jidsToUnblock, true));
		unblockRequest->send();
	}
	if (!jidsToBlock.empty() || !jidsToUnblock.empty()) {
		assert(blockListEditorWidget_);
		blockListEditorWidget_->setBusy(true);
		blockListEditorWidget_->setError("");
	} else {
		blockListEditorWidget_->hide();
	}
}

void BlockListController::handleBlockListChanged() {
	if (blockListEditorWidget_) {
		std::vector<JID> jidsToBlock;
		std::vector<JID> jidsToUnblock;

		blockListDifferences(blockListEditorWidget_->getCurrentBlockList(), jidsToUnblock, jidsToBlock);
		blockListBeforeEdit = blockListManager_->getBlockList()->getItems();

		foreach (const JID& jid, jidsToBlock) {
			if (std::find(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid) == blockListBeforeEdit.end()) {
				blockListBeforeEdit.push_back(jid);
			}
		}

		foreach (const JID& jid, jidsToUnblock) {
			blockListBeforeEdit.erase(std::remove(blockListBeforeEdit.begin(), blockListBeforeEdit.end(), jid), blockListBeforeEdit.end());
		}

		blockListEditorWidget_->setCurrentBlockList(blockListBeforeEdit);
	}
}

}
